/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package biofilter.records;

import biofilter.util.StringUtil;
import biofilter.util.ArrayUtil;
import biofilter.util.MappedPosition;
import java.text.ParseException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
 *
 * @author Daniel Quest
 * @date   Dec 18th 2009
 * Example:
 * HWI-EAS127_3_4_1_774_879\t-\tctg93  [organism=Burkholderia cenocepacia AU 1054] [tech=wgs] [gcode=11] [strain=AU 1054]\t419184\tAANTTGACCGCCTGGGGAGTACGGTCGCAAGATTAAAACTC\tIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII\t2\t38:G>N$
 */
public class BowTieRecord implements Record, MappedPosition {

    /**
 * #The alignment file contains a list of rows that look like:
HWI-EAS127_3_4_1_774_879       -       ctg93  [organism=Burkholderia cenocepacia AU 1054] [tech=wgs] [gcode=11] [strain=AU 1054]       419184  AANTTGACCGCCTGGGGAGTACGGTCGCAAGATTAAAACTC       IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII       2       38:G>N
Query_id                       Strand  SubjID                                                                                          sstart  readSeq                                         Alignment                                       #missmatch BaseChangeDescription
Here are all the bowtie results for HWI-EAS127_3_4_1_774_879
HWI-EAS127_3_4_1_774_879        -       ctg93  [organism=Burkholderia cenocepacia AU 1054] [tech=wgs] [gcode=11] [strain=AU 1054]       419184  AANTTGACCGCCTGGGGAGTACGGTCGCAAGATTAAAACTC       IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII       2       38:G>N
HWI-EAS127_3_4_1_774_879        -       ctg94  [organism=Burkholderia cenocepacia AU 1054] [tech=wgs] [gcode=11] [strain=AU 1054]       97654   AANTTGACCGCCTGGGGAGTACGGTCGCAAGATTAAAACTC       IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII       2       38:G>N
HWI-EAS127_3_4_1_774_879        -       ctg95  [organism=Burkholderia cenocepacia AU 1054] [tech=wgs] [gcode=11] [strain=AU 1054]       58243   AANTTGACCGCCTGGGGAGTACGGTCGCAAGATTAAAACTC       IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII       2       38:G>N
HWI-EAS127_3_4_1_774_879        +       ctg95  [organism=Burkholderia cenocepacia AU 1054] [tech=wgs] [gcode=11] [strain=AU 1054]       1786951 GAGTTTTAATCTTGCGACCGTACTCCCCAGGCGGTCAANTT       IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII       2       38:C>N
HWI-EAS127_3_4_1_774_879        +       ctg95  [organism=Burkholderia cenocepacia AU 1054] [tech=wgs] [gcode=11] [strain=AU 1054]       2816492 GAGTTTTAATCTTGCGACCGTACTCCCCAGGCGGTCAANTT       IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII       2       38:C>N
HWI-EAS127_3_4_1_774_879        +       ctg95  [organism=Burkholderia cenocepacia AU 1054] [tech=wgs] [gcode=11] [strain=AU 1054]       3048770 GAGTTTTAATCTTGCGACCGTACTCCCCAGGCGGTCAANTT       IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII       2       38:C>N
#example blast tab deliminated output:
q - query the thing you are blasting
q - the source database you are blasting against
   1. query id (from the search)
   2. strand (+/-)
   3. subject id (from the formated database)
   4. subject offset (0 indexed start position relative to the subject)
   5. query sequence (sequence of the query, or reverse complement of query sequence)
   6. quality (query quality) ~~only relevent if fastq file...
   7. mismatches
   8. substitutions

 */
    private StringUtil sute = null;
    private ArrayUtil aute = null;
    private String record = null;  //HWI-EAS127_3_4_1_774_879        -       ctg93  [organism=Burkholderia cenocepacia AU 1054] [tech=wgs] [gcode=11] [strain=AU 1054]       419184  AANTTGACCGCCTGGGGAGTACGGTCGCAAGATTAAAACTC       IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII       2       38:G>N

    private String query_id = null;
    private char strand = '.';  //. is convention for unknown strand else +/-
    private String subject_id = null;
    private int subject_offset = Integer.MIN_VALUE;
    private String query_sequence = null; //note that this sequence will be changed to reflect the matching sequence... reconstructing the actual query sequence requires parsing and applying the substitutions
    private String quality = null; //IIIIIII...
    private int mismatches = Integer.MIN_VALUE;
    private int col6 = Integer.MIN_VALUE;
    private String substitutions = null;

    public BowTieRecord(){
        sute = new StringUtil();
        //aute = new ArrayUtil();
    }
    public BowTieRecord(String line){
        set(line);
        sute = new StringUtil();
        //aute = new ArrayUtil();
    }
    public void set(String line){
        this.record = line;
        query_id = null;
        strand = '.';  //. is convention for unknown strand else +/-
        subject_id = null;
        subject_offset = Integer.MIN_VALUE;
        query_sequence = null; //note that this sequence will be changed to reflect the matching sequence... reconstructing the actual query sequence requires parsing and applying the substitutions
        quality = null; //IIIIIII...
        mismatches = Integer.MIN_VALUE;
        substitutions = null;
    }
    public String get(){ return this.record; }

    private void parse(){
        try {
            //System.err.println(this.record);
            Integer pInt = new Integer("0");
            String[] rawTokens = sute.splitOnTab(record.trim(), 8);
            //aute.printArray(rawTokens);
            query_id = rawTokens[0].trim();
            strand = rawTokens[1].trim().charAt(0); //. is convention for unknown strand else +/-
            subject_id = rawTokens[2].trim();
            subject_offset = pInt.parseInt(rawTokens[3].trim());
            query_sequence = rawTokens[4].trim();
            quality = rawTokens[5].trim();
            col6 = pInt.parseInt(rawTokens[6].trim()); //no idea what this really means... it is not number missmatches!
            if (rawTokens[7] != null){
                substitutions = rawTokens[7].trim();
                substitutionToMismatchCount(substitutions);
            }else{
                substitutions = "";
                substitutionToMismatchCount(substitutions);
            }
            return;
        } catch (ParseException ex) {
            Logger.getLogger(BowTieRecord.class.getName()).log(Level.SEVERE, this.record, ex);
        }
    }

    private int substitutionToMismatchCount(String s){
        int missmatches = 0;
        if(s.equalsIgnoreCase("")){
            this.mismatches = 0;
            return 0;
        }
        String countString = ",";
        if (s.length() > 3) missmatches++;
        int numberOfCommas = s.split("\\Q"+countString+"\\E", -1).length - 1;
        if (numberOfCommas > 0) missmatches += numberOfCommas;
        this.mismatches = missmatches;
        return missmatches;
    }


    public int getMismatches() {
        //System.err.println(mismatches);
        //System.err.println(Integer.MIN_VALUE);
        if (mismatches == Integer.MIN_VALUE){  parse();  } //lazy initialization
        return mismatches;
    }
    /**
     * getWeight aproximates an alignment score by returning matches/read_length
     * default values are same as blast: 1 for every match, -2 for every mismatch
     */
    public double getWeight(){
        double score = 0, mat = 1, mis = 0;
        if( query_sequence == null ){ parse(); }
        mat = (this.query_sequence.length() - this.mismatches ) * matchScore;
        mis = this.mismatches * this.mismatchScore;
        score = mat + mis;
        return score;
    }

    /**
     * Following scores are for generating an alignment based score.
     * NOTE: Gap insertion (I) and deletion (D) are not needed because
     * the bowtie algorithm can not calculate on gaps.
     */
    private double matchScore = 1.0;
    public void setMatchScore(double m){ this.matchScore = m; }
    private double mismatchScore = -2.0;
    public void setMisMatchScore(double m){ this.mismatchScore = m; }

    public String getQuality() {
        if( quality == null ){ parse(); }
        return quality;
    }

    public String getQueryID() {
        if( query_id == null ){ parse(); }
        return query_id;
    }

    public String getQuery_sequence() { 
        if( query_sequence == null ){ parse(); }
        return query_sequence;
    }

    public char getStrand() { 
        if( strand != '+' || strand != '-' ){ parse(); }
        return strand;
    }

    public String getSubjectID() {
        if( subject_id == null ){ parse(); }
        return subject_id;
    }

    public int getSubject_offset() {
        if( subject_offset == Integer.MIN_VALUE ){ parse(); }
        return subject_offset;
    }

    public String getSubstitutions() {
        if( substitutions == null ){ parse(); }
        return substitutions;
    }


    /**
     * IMPORTANT: getStart() and getEnd() will report starts and ends in the same way as blast and NOT as you may expect them
     * for example indexes are base 1 and NOT base 0 as in the original bowtie output.
     * @return
     */
    public int getStart(){
        if( subject_offset == Integer.MIN_VALUE ){ parse(); }
        int seqLen = query_sequence.length();
        if(strand == '-'){
            return subject_offset + seqLen;
        }else {
            return subject_offset + 1;  //because bowtie is base zero!
        }
    }

    public int getEnd(){
        if( query_sequence == null || subject_offset == Integer.MIN_VALUE ){ parse(); }
        int seqLen = query_sequence.length();
        if(strand == '-'){
            return subject_offset + 1;
        }else {
            return seqLen + subject_offset;
        }
    }

    public int getStop() {
        return getEnd();
    }

    public int length() {
        return query_sequence.length();
    }

}


